//===============================================================================
// Microsoft patterns & practices
// Mobile Client Software Factory - July 2006
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;

namespace Microsoft.Practices.Mobile.ObjectBuilder
{
	/// <summary>
	/// Strategy that tries to locate an <see cref="IPolicyProvider"/> for the 
	/// type being build.
	/// </summary>
	/// <remarks>
	/// This strategy must be added to the chain both at the very first stage 
	/// (<see cref="BuilderStage.PreCreation"/>) and the last stage 
	/// (<see cref="BuilderStage.PostInitialization"/>. This is required in 
	/// order for the strategy to work as expected, loading up policies from 
	/// the provider first on build up, and first on tear down. Failure to 
	/// add the strategy at both ends of the strategies list will result in 
	/// improper behavior. 
	/// <para>
	/// This is required because the builder behavior is to run the build up 
	/// chain in reverse order on tear down. If the strategy is not added 
	/// at both ends of the list, it will be run *last* on tear down, too 
	/// late for providing policies to other strategies.
	/// </para>
	/// </remarks>
	public class PolicyProviderStrategy : BuilderStrategy
	{
		object keyValue = new object();

		/// <summary>
		/// Tries to load a policy provider for the type, and calls it if found.
		/// </summary>
		/// <remarks>
		/// Probing rules for the policy provider are:
		/// <list type="bullet">
		/// <item>
		/// <term>Provider type name</term>
		/// <description>Equals the <paramref name="typeToBuild"/> full name with the <c>PolicyProvider</c> suffix appended.</description>
		/// </item>
		/// <item>
		/// <term>Assembly name</term>
		/// <description>The provider type is first searched in the same assembly as the <paramref name="typeToBuild"/>, 
		/// and later in an assembly with the same name as the <paramref name="typeToBuild"/> with the <c>.obgen</c> 
		/// suffix appended.</description>
		/// </item>
		/// </list>
		/// The provider type, if found, must implement the <see cref="IPolicyProvider"/> interface.
		/// <para>
		/// Generic types do not support having providers at this point.
		/// </para>
		/// </remarks>
		/// <example>
		/// Given the full type name <c>MyNamespace.MyType, MyAssembly</c>, the strategy will first try to load the type 
		/// <c>MyNamespace.MyTypePolicyProvider, MyAssembly</c> and if not found, the type 
		/// <c>MyNamespace.MyTypePolicyProvider, MyAssembly.obgen</c>
		/// </example>
		public override object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
		{
			Guard.ArgumentNotNull(context, "context");
			Guard.ArgumentNotNull(typeToBuild, "typeToBuild");

			Key key = new Key(new DependencyResolutionLocatorKey(typeToBuild, idToBuild));
			if (!context.Locator.Contains(key, SearchMode.Local))
			{
				if (!typeToBuild.IsGenericType)
				{
					IPolicyProvider provider = GetPolicyProvider(typeToBuild);
					if (provider != null)
					{
                        PolicyList pl = provider.GetPolicies(context.Policies, typeToBuild, idToBuild);
						context.Policies.AddPolicies(pl);

					}
				}
				context.Locator.Add(key, keyValue);
			}
			else
			{
				// Remove the trace
				context.Locator.Remove(key);
			}

			return base.BuildUp(context, typeToBuild, existing, idToBuild);
		}

		/// <summary>
		/// Tries to retrieve policies from a provider corresponding to the current 
		/// item type. See <see cref="BuildUp"/> for the provider probing rules.
		/// </summary>
		public override object TearDown(IBuilderContext context, object item)
		{
			Guard.ArgumentNotNull(context, "context");
			Guard.ArgumentNotNull(item, "item");

			Type itemType = item.GetType();
			Key key = new Key(new DependencyResolutionLocatorKey(itemType, null));
			if (!context.Locator.Contains(key, SearchMode.Local))
			{
				if (!itemType.IsGenericType)
				{
					IPolicyProvider provider = GetPolicyProvider(itemType);
					if (provider != null)
					{
						context.Policies.AddPolicies(provider.GetPolicies(context.Policies, itemType, null));
					}
				}
				context.Locator.Add(key, keyValue);
			}
			else
			{
				// Remove the trace
				context.Locator.Remove(key);
			}

			return base.TearDown(context, item);
		}

		private static IPolicyProvider GetPolicyProvider(Type typeToBuild)
		{
			IPolicyProvider provider = null;
			string providerTypeName = typeToBuild.FullName + "PolicyProvider";
			string providerFullName = providerTypeName + ", " + typeToBuild.Assembly.FullName;

			Type providerType = Type.GetType(providerFullName);

			if (providerType == null)
			{
				providerFullName = providerTypeName + ", " + typeToBuild.Assembly.GetName().Name + ".obgen";
				providerType = Type.GetType(providerFullName);
			}

			if (providerType != null)
			{
				Guard.TypeIsAssignableFromType(typeof(IPolicyProvider), providerType, typeToBuild);
				provider = (IPolicyProvider)Activator.CreateInstance(providerType);
			}

			return provider;
		}

		// Class used to signal that the provider has already run and must not do further work. 
		// This behavior is required because this strategy is special in that it must 
		// run first on BuildUp AND TearDown. This is not the behavior for all 
		// other strategies, where TearDown runs the chain in reverse order. 
		// In order to overcome this limitation, we add the strategy both at 
		// the beginning and the end of the chain, and use a key in the locator to 
		// signal that it has already run in the current build.
		class Key
		{
			DependencyResolutionLocatorKey key;

			public Key(DependencyResolutionLocatorKey key)
			{
				this.key = key;
			}

			public override bool Equals(object obj)
			{
				if (obj == null || GetType() != obj.GetType())
				{
					return false;
				}

				Key other = (Key)obj;

				return this.key.Equals(other.key);
			}

			public override int GetHashCode()
			{
				return typeof(Key).AssemblyQualifiedName.GetHashCode() ^ key.GetHashCode();
			}
		}
	}
}
